<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Clamping of AudioParam Time
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // Fairly arbitrary sample rate and render frames.
      let sampleRate = 24000;
      let renderFrames = 1024;

      let audit = Audit.createTaskRunner();

      audit.define('setValue', (task, should) => {
        let suspendFrame = 128;
        createGraph({
          suspendFrame: suspendFrame,
          method: 'setValueAtTime',
          initialGain: 0,
          arg0: 1,
        })
            .then(function(resultBuffer) {
              // Just verify that the cosine wave actually started at
              // suspendFrame.
              let result = resultBuffer.getChannelData(0);

              should(
                  result.slice(0, suspendFrame),
                  'setValue: Output[0-' + (suspendFrame - 1) + ']')
                  .beConstantValueOf(0);
              should(
                  result.slice(suspendFrame),
                  'setValue: Output[' + suspendFrame + '-' +
                      (renderFrames - 1) + ']')
                  .beConstantValueOf(1);
            })
            .then(() => task.done());
      });

      audit.define('linear', (task, should) => {
        let suspendFrame = 128;
        createGraph({
          suspendFrame: suspendFrame,
          method: 'linearRampToValueAtTime',
          initialGain: 1,
          arg0: 0.5
        })
            .then(function(resultBuffer) {
              // Just verify that the cosine wave actually started at
              // suspendFrame.
              let result = resultBuffer.getChannelData(0);

              should(
                  result.slice(0, suspendFrame),
                  'linear: Output[0-' + (suspendFrame - 1) + ']')
                  .beConstantValueOf(1);
              should(
                  result.slice(suspendFrame),
                  'linear: Output[' + suspendFrame + '-' + (renderFrames - 1) +
                      ']')
                  .beConstantValueOf(0.5);

            })
            .then(() => task.done());
      });

      audit.define('exponential', (task, should) => {
        let suspendFrame = 128;
        createGraph({
          suspendFrame: suspendFrame,
          method: 'exponentialRampToValueAtTime',
          initialGain: 1,
          arg0: 0.5
        })
            .then(function(resultBuffer) {
              // Just verify that the cosine wave actually started at
              // suspendFrame.
              let result = resultBuffer.getChannelData(0);

              should(
                  result.slice(0, suspendFrame),
                  'exponential: Output[0-' + (suspendFrame - 1) + ']')
                  .beConstantValueOf(1);
              should(
                  result.slice(suspendFrame),
                  'exponential: Output[' + suspendFrame + '-' +
                      (renderFrames - 1) + ']')
                  .beConstantValueOf(0.5);
            })
            .then(() => task.done());
      });

      audit.define('setTarget', (task, should) => {
        let suspendFrame = 128;
        createGraph({
          suspendFrame: suspendFrame,
          method: 'setTargetAtTime',
          initialGain: 1,
          arg0: 0.5,
          moreArgs: 0.1
        })
            .then(function(resultBuffer) {
              // Just verify that the cosine wave actually started at
              // suspendFrame.
              let result = resultBuffer.getChannelData(0);

              should(
                  result.slice(0, suspendFrame),
                  'setTarget: Output[0-' + (suspendFrame - 1) + ']')
                  .beConstantValueOf(1);
              // For the samples past the suspend time, we only care that first
              // value is 1 and that the rest are not zero.
              should(
                  result[suspendFrame],
                  'setTarget: Output[' + suspendFrame + ']')
                  .beEqualTo(1);

              let positive = result.slice(suspendFrame + 1).every(x => x > 0);
              should(
                  positive,
                  'Output[' + (suspendFrame + 1) + '-' + (renderFrames - 1) +
                      '] contains only positive values')
                  .beEqualTo(true);
            })
            .then(() => task.done());
      });

      audit.define('setValueCurve', (task, should) => {
        let suspendFrame = 128;
        createGraph({
          suspendFrame: suspendFrame,
          method: 'setValueCurveAtTime',
          initialGain: 1,
          arg0: Float32Array.from([2, 3]),
          moreArgs: 0.1
        })
            .then(function(resultBuffer) {
              // Just verify that the cosine wave actually started at
              // suspendFrame.
              let result = resultBuffer.getChannelData(0);

              should(
                  result.slice(0, suspendFrame),
                  'setValueCurve: Output[0-' + (suspendFrame - 1) + ']')
                  .beConstantValueOf(1);

              // The selected curve contains values greater than or equal to 2.
              // Just verify that all values are greater than or equal to 2.
              let biggerThan2 = result.slice(suspendFrame).every(x => x >= 2);
              should(
                  biggerThan2,
                  'setValueCurve: Output[' + suspendFrame + '-' +
                      (renderFrames - 1) + ']')
                  .beEqualTo(true);
            })
            .then(() => task.done());
      });


      // Create the test graph consisting of a constant source followed by a
      // gain node.  The gain node automations will be tested.  |options|
      // specifies the parameters for the test including
      //
      //   suspendFrame: time at which we schedule the automation call
      //   method: the name of automation method to be tested
      //   initialGain: the initial gain at time 0 for the gain node
      //   arg0: the first argument to be supplied to the automation method.
      //   moreArgs: An array of any additional arguments for the automation
      //   method.
      function createGraph(options) {
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        let cosineWave = new PeriodicWave(context, {real: [0, 1]});

        let src = new OscillatorNode(
            context, {periodicWave: cosineWave, frequency: 0});

        // The gain node whose automations we're testing.
        let gain = new GainNode(context, {gain: 0});

        src.connect(gain).connect(context.destination);

        gain.gain.setValueAtTime(options.initialGain, 0);

        // Suspend rendering so that we can call the automation method at the
        // right time.  |startTime| is the time for the automation method. It
        // must be some time before the suspend time.
        let suspendFrame = options.suspendFrame;
        let startTime = (suspendFrame / 2) / context.sampleRate;
        context.suspend(suspendFrame / context.sampleRate)
            .then(function() {
              // Call the appropriate automation method with the desired
              // automation value, time, and any other arguments needed.
              gain.gain[options.method](
                  ...[options.arg0, startTime, options.moreArgs]);
            })
            .then(context.resume.bind(context));

        // Start the source and begin rendering, returning the promise from
        // rendering.
        src.start();

        return context.startRendering();
      }

      audit.run();
    </script>
  </body>
</html>
